// const FILE_IS_EMPTY_AT_THESE_LIB = '指定驱动器无文件登记记录!'
// const FILE_IS_UNASSOCIATED_INDEX_AT_THESE_LIB = '指定驱动器文件集无索引关联记录!'
// const INDEX_IS_UNASSOCIATED_TAG_AT_THESE_LIB = '指定驱动器索引集无标签关联记录!'
// const TAG_IS_UNASSOCIATED_INDEX_AT_THESE_LIB = '指定驱动器索引集无关联对应标签记录!'
// const SELECT_SUCCESS_AT_THESE_LIB = '指定驱动器成功查询到记录!'
// const TITLE_SUBTITLE_DESCRIPTION_IS_UNASSOCIATED_INDEX = '标题不存在索引!'
// const QUERY_IS_UNASSOCIATED_INDEX_AT_THESE_LIB = '指定驱动器索引集无关联关键词的索引!'
const SELECT_EXCEPTION = '查询出错!'
const SELECT_SUCCESS = '成功查询到记录!'
const QUERY_IS_UNASSOCIATED_INDEX = '关键词未关联索引!'

const SEARCH_RESULT_FAILED = 0
const SEARCH_RESULT_SUCCESS = 1
const SEARCH_RESULT_NOTFOUND = 2


class SearchResult {
    code
    message
    result
    /**
     * 
     * @param {String} msg 查询信息 
     * @param {Number} code 查询状态 SEARCH_RESULT_FAILED失败(异常) SEARCH_RESULT_SUCCESS成功 SEARCH_RESULT_NOTFOUND无记录
     * @param {Set|undefined} res 结果
     */
    constructor(msg, code, res) {
        this.message = msg
        this.code = code
        this.result = res
    }
}
class Search {
    static keyWordStyle = 'color:#F14C4C;'
    static subtitleStyle = 'color:#DCDCAA;'
    static titleStyle = 'color:#569CD6;'
    static tagStyle = 'color:#9CDCFE;'
    static authorStyle = 'color:#FB7299;'
    static generalStyle = 'color:#9A9A9A;'
    static tagListScoring = (pre, cur) => pre.score + cur.score
    db
    constructor(dataBase) {
        this.db = dataBase
    }
    /**
     * 
     * @param {Set<String>} position 
     * @param {Number} searchMode 混合/直接/全文
     * @param {Number} type 
     * @param {Set<String>} query 
     * @returns 
     */
    searchRoute(searchMode, type, query) {
        try {
            switch (searchMode) {
                case 0:
                    return this.hashRetrieval(type, query)
                case 1:
                    return this.loopRetrieval(type, query)
            }
        } catch (error) {
            console.log(SELECT_EXCEPTION, SEARCH_RESULT_FAILED, error)
        }
    }
    async hashRetrieval(type, query) {
        /**
         * 标签索引
         * 标题索引
         * 
         * 标签
         *  根据标签获取关联index,存入MAP(去重)中
         * 标题
         *  根据index标题获取关联index,存入MAP(去重)中
         */
        const allTypeFilter = () => true
        const onlyTypeFilter = index => index.type === type
        const indexMap = new Map();
        const setIndexMap = index => indexMap.set(index.id, index)
        const indexParams = []

        if (type === 0)
            for (const text of query)
                for (let i = 1; i <= 6; i++)
                    indexParams.push([text, i])
        else
            for (const text of query)
                indexParams.push([text, type])
        /**标签检索 */
        const tagIdArray = await this.db.tag._BatchGetByIndex('text', [...query], { map: tag => tag.id })

        if (tagIdArray.length > 0) {
            const indexIdArray = await this.db.index_tag_relationship._BatchGetByIndex('tagId', tagIdArray, { map: relationship => relationship.indexId, isUQ: true })
            if (indexIdArray.length > 0) {
                const indexByTagArray = await this.db.index._BatchGet(indexIdArray, { filter: type === 0 ? allTypeFilter : onlyTypeFilter })
                indexByTagArray.forEach(setIndexMap)
            }
        }

        /**标题检索 */
        const indexByTitleArray = await this.db.index._BatchGetByIndex('titleType', indexParams, { filter: index => !(indexMap.has(index.id)) })
        const indexBySubTitleArray = await this.db.index._BatchGetByIndex('subTitleType', indexParams, { filter: index => !(indexMap.has(index.id)) })
        indexByTitleArray.forEach(setIndexMap)
        indexBySubTitleArray.forEach(setIndexMap)

        /**渲染 */
        const resultList = await this.resultRendering(query, indexMap)

        return resultList.length < 1 ? new SearchResult(QUERY_IS_UNASSOCIATED_INDEX, SEARCH_RESULT_NOTFOUND) : new SearchResult(SELECT_SUCCESS, SEARCH_RESULT_SUCCESS, resultList)
    }
    async loopRetrieval(type, query) {
        /**
         * 遍历标签
         * 遍历索引标题和描述
         * contain算法
         * 
         * 遍历标签
         *  根据标签获取关联index,存入MAP(去重)中
         * 遍历索引标题和描述
         *  根据index标题获取关联index,存入MAP(去重)中
         */
        const allTypeFilter = () => true
        const onlyTypeFilter = index => index.type === type
        const indexMap = new Map();
        /**标签遍历 */
        const tagIdArray = await this.db.tag._BatchGetByIndex('state', [1], {
            map: tag => tag.id, filter: tag => {
                for (const str of query)
                    return contain(tag.text, str)

            }
        })
        if (tagIdArray.length > 0) {
            const indexIdArray = await this.db.index_tag_relationship._BatchGetByIndex('tagId', tagIdArray, { map: relationship => relationship.indexId, isUQ: true })
            if (indexIdArray.length > 0) {
                const indexByTagArray = await this.db.index._BatchGet(indexIdArray, { filter: type === 0 ? allTypeFilter : onlyTypeFilter })
                indexByTagArray.forEach(index => indexMap.set(index.id, index))
            }
        }
        const indexParams = new Array()
        if (type === 0)
            for (let i = 1; i <= 6; i++)
                indexParams.push(i)
        else
            indexParams.push(type)
        /**索引检索 */
        const indexArray = await this.db.index._BatchGetByIndex('type', indexParams,
            {
                filter: (index) => {
                    if (!indexMap.has(index.id))
                        for (const str of query)
                            return contain(index.title, str) ? true : contain(index.subtitle, str) ? true : contain(index.description, str) ? true : false
                    else
                        return false
                }
            }
        )
        indexArray.forEach(index => indexMap.set(index.id, index))
        /**渲染 */
        const resultList = await this.resultRendering(query, indexMap)
        return resultList.length === 0 ? new SearchResult(QUERY_IS_UNASSOCIATED_INDEX, SEARCH_RESULT_NOTFOUND) : new SearchResult(SELECT_SUCCESS, SEARCH_RESULT_SUCCESS, resultList)
    }
    async resultRendering(querys, indexMap) {
        const annotator = new Annotator(querys)
        const resultList = []
        const indexIterator = indexMap.values()
        const tagRenderingCacheMap = new Map()

        for (const index of indexIterator) {
            const tagRelationship = await this.db.index_tag_relationship.getByIndex('indexId', index.id, false);
            const tagList = await this.db.tag._BatchGet(tagRelationship.map(relationship => relationship.tagId));
            const authorArray = [];
            const tagArray = [];

            /**解析tag */
            for (const tag of tagList) {
                let resultTag
                let isContained = false
                let includeScore = 0
                for (const key of querys) {
                    const keyL = key.toLowerCase()
                    const tL = tag.text.toLowerCase()
                    if (keyL.includes(tL)) {
                        includeScore++
                        isContained = true
                        break
                    }
                }
                if (isContained) {
                    resultTag = new SearchResultTag(annotator.span(tag.text, Search.keyWordStyle), tag, includeScore)
                    tagRenderingCacheMap.set(resultTag.source.id, resultTag)
                    if (tag.type === 0)
                        tagArray.push(resultTag);
                    else
                        authorArray.push(resultTag);
                } else {
                    if (!tagRenderingCacheMap.has(tag.id)) {
                        if (tag.type === 0) {
                            const tagAnnotateResult = annotator.annotate_span(tag.text, Search.keyWordStyle, Search.tagStyle)
                            resultTag = new SearchResultTag(tagAnnotateResult.text, tag, tagAnnotateResult.score)
                            tagRenderingCacheMap.set(resultTag.source.id, resultTag)
                            tagArray.push(resultTag);
                        } else {
                            const tagAnnotateResult = annotator.annotate_span(tag.text, Search.keyWordStyle, Search.authorStyle)
                            resultTag = new SearchResultTag(tagAnnotateResult.text, tag, tagAnnotateResult.score)
                            tagRenderingCacheMap.set(resultTag.source.id, resultTag)
                            authorArray.push(resultTag);
                        }
                    } else {
                        if (tag.type === 0)
                            tagArray.push(tagRenderingCacheMap.get(tag.id));
                        else
                            authorArray.push(tagRenderingCacheMap.get(tag.id));
                    }
                }
            }
            const coverRelationship = await this.db.index_cover_relationship.getByIndex('indexIdSequence', [index.id, 0], true)
            const cover = coverRelationship ? await this.db.file.get(coverRelationship.fileId) : false

            /**解析index */
            const titleAnnotateResult = annotator.annotate_span(index.title, Search.keyWordStyle, Search.titleStyle)
            const subTitleAnnotateResult = annotator.annotate_span(index.subtitle, Search.keyWordStyle, Search.subtitleStyle)
            const descriptionAnnotateResult = annotator.annotate_span(index.description, Search.keyWordStyle, Search.generalStyle)
            const score = titleAnnotateResult.score + subTitleAnnotateResult.score + descriptionAnnotateResult.score

            resultList.push(
                new SearchResultIndex(
                    titleAnnotateResult.text,
                    subTitleAnnotateResult.text,
                    descriptionAnnotateResult.text,
                    score,
                    tagArray,
                    authorArray,
                    cover,
                    (tagArray.length > 0 ? tagArray.reduce(Search.tagListScoring) : 0) +
                    (authorArray.length > 0 ? authorArray.reduce(Search.tagListScoring) : 0) +
                    score,
                    index
                )
            )
        }
        return resultList
    }
}
class Annotator {
    regExp
    constructor(targets) {
        const regularList = []
        const strSet = new Set()

        targets.forEach(key => {
            //(\\b${key}\\b)|(\\B${key}\\B)`
            if (key.length > 1)
                strSet.add(`(${key}\\B)|(${key}\\b)`)
            else if (key.length === 1)
                strSet.add(`[${key}]+`)
        })
        strSet.forEach(val => regularList.push(val))

        //this.regExp = new RegExp(`(${regularList.join('|')})+`, "ig")
        this.regExp = new RegExp(`${regularList.join('|')}`, "ig")
    }
    /**
     * 
     * @param {*} source 
     * @param {Set<String>} targets 
     * @param {*} relevantStyle 
     * @param {*} unrelatedStyle 
     * @returns 
     */
    annotate_span(source, relevantStyle, unrelatedStyle) {

        const result = {
            score: 0,
            text: ''
        }
        const scoring = (val) => {
            result.score++
            return this.span(val, relevantStyle)
        }
        result.text = `<span style="${unrelatedStyle}">${source.replaceAll(this.regExp, scoring)}</span>`
        return result
    }
    span(val, style) {
        return `<span style="${style}">${val}</span>`
    }
}
class SearchResultIndex {
    title
    subtitle
    description
    score
    totalScore
    tagArray // text,state,createDate,source
    authorArray // text,state,createDate,source
    cover
    source
    constructor(title, subtitle, description, score, tagArray, authorArray, cover, totalScore, source) {
        this.title = title
        this.subtitle = subtitle
        this.description = description
        this.tagArray = tagArray
        this.authorArray = authorArray
        this.cover = cover
        this.source = source
        this.score = score
        this.totalScore = totalScore
    }
}
class SearchResultTag {
    text
    score
    source
    constructor(text, source, score) {
        this.text = text
        this.source = source
        this.score = score
    }
}


export { Search }